/******************************************************************************
 * $Id: funcgraph.c,v 1.68 2004/09/22 01:14:22 jimb Exp $
 * @file
 * @author	Jim Brooks http://www.jimbrooks.org
 * @brief	Functions to build a graph of function relationships.
 ******************************************************************************/

#include "common.h"
#include "funcgraph.h"

#include "parser.h"
#include "tags.h"
#include "module.h"
#include "file.h"
#include "widgets.h"
#include "statusbar.h"
#include "debug.h"

#ifdef HYPERSRC_DEBUG
#define MEASURE_GRAPHING 1
#endif

static GSList*  pGslistFuncCall		= NULL;		/* list of calls in one func def */
static char*    cur_pModuleName		= NULL;		/* current module being parsed */
static GList*   cur_pLinkModule		= NULL;		/* current module being parsed */
static char     table1IgnoreFuncName[256];
static char     table2IgnoreFuncName[256];
static int      cntFuncDefs  = 0;
static int      cntFuncCalls = 0;

/*****************************************************************************
 * Must be called to initialize utility functions.
 *****************************************************************************/
void
InitFuncgraph( void )
{
   CLEAR( table1IgnoreFuncName, sizeof(table1IgnoreFuncName) );
   CLEAR( table2IgnoreFuncName, sizeof(table2IgnoreFuncName) );
   table1IgnoreFuncName[ (int)'i' ] = 1; table2IgnoreFuncName[ (int)'f' ] = 1;  /* if      */
   table1IgnoreFuncName[ (int)'r' ] = 1; table2IgnoreFuncName[ (int)'e' ] = 1;  /* return  */
   table1IgnoreFuncName[ (int)'s' ] = 1; table2IgnoreFuncName[ (int)'i' ] = 1;  /* sizeof  */
   table1IgnoreFuncName[ (int)'w' ] = 1; table2IgnoreFuncName[ (int)'h' ] = 1;  /* while   */
   table1IgnoreFuncName[ (int)'f' ] = 1; table2IgnoreFuncName[ (int)'o' ] = 1;  /* for     */
   table1IgnoreFuncName[ (int)'s' ] = 1; table2IgnoreFuncName[ (int)'w' ] = 1;  /* switch  */
   table1IgnoreFuncName[ (int)'d' ] = 1; table2IgnoreFuncName[ (int)'e' ] = 1;  /* defined */
   table1IgnoreFuncName[ (int)'~' ] = 1; table2IgnoreFuncName[ (int)0   ] = 1;  /* ~       */

   return;
}

/*****************************************************************************
 * Callback to free the hash table of func-like tags of one module.
 *****************************************************************************/
static void
cb_FreeHashFuncTags( gpointer pKey,
                          gpointer pData,
                          gpointer unused )
{
   module_t* pModule = (module_t*) pData;
   g_hash_table_destroy( pModule->pHashFuncTags );
   pModule->pHashFuncTags = NULL;
}

/******************************************************************************
 * Build a graph of functions.
 * (see types.h for an illustration of the graph)
 ******************************************************************************/
void
BuildFuncGraph( void )
{
   char*	pBuf		= NULL;
   guint	moduleSize  = 0;

#ifdef HYPERSRC_PEDANTIC
  /*
   * Prerequisites.
   */
   g_assert( pGlistTags );
   g_assert( pGlistModuleNames );
   g_assert( pHashModules );
   g_assert( pHashFuncTagsAll );
#endif

   pHashTagsFabricated = g_hash_table_new( g_str_hash, g_str_equal );

#if MEASURE_GRAPHING
   g_get_current_time( &timeval1 );
#endif

  /*
   * Pass the contents of each module to the function call parser.
   */
   for ( cur_pLinkModule = pGlistModuleNames;
         cur_pLinkModule;
         cur_pLinkModule = cur_pLinkModule->next )
   {
      pBuf				= NULL;
      pGslistFuncCall   = NULL;
      cur_pModuleName	= (char*) cur_pLinkModule->data;
g_assert( cur_pModuleName );

      ProcessPendingEvents();

     /*
      * The parser only supports C/C++/Java source files.
      */
      if (    ! (                     IsSrcFileC(         cur_pModuleName ) )
           && ! (browsingCPlusPlus && IsSrcFileCPlusPlus( cur_pModuleName ) )
           && ! (browsingJava      && IsSrcFileJava(      cur_pModuleName ) ) )
         continue;

     /*
      * Read the contents of this module.
      */
      pBuf = ModuleBuffer( cur_pModuleName, &moduleSize );
      if ( UX(pBuf == NULL  ||  moduleSize <= 0) )
      {
         /* --module.c has already called Warning()--*/
         /*Warning( "Couldn't open module %s for parsing.", cur_pModuleName );*/
         continue;	/* can happen if a src file is empty, eg Mesa3D has a few */
      }
#if !defined(HYPERSRC_SPEED)  &&  !defined(HYPERSRC_PROFILE)
      StartupStatusMsg( "Graphing functions: ", cur_pModuleName, " " );
#endif
     /*
      * Parse function definitions, calls, inlines in this module.
      */
      ParseFuncsInModule( pBuf, moduleSize, cur_pModuleName );

     /*
      * To next module...
      */
      FreeModuleBuffer( cur_pModuleName );
      
      /*
       * if for speed  : reload it (lv)
       * if for memory : don't reload it (lv)
       */
      //ModuleBuffer( cur_pModuleName, &moduleSize);
   }

#if MEASURE_GRAPHING
   g_get_current_time( &timeval2 );
   PrintUsecElapsed( &timeval1, &timeval2 );
#endif

   StatusMsg( "func defs  = %d \n", cntFuncDefs );
   StatusMsg( "func calls = %d \n", cntFuncCalls );

  /*
   * Free data structs that are no longer needed.
   */
   g_hash_table_destroy( pHashTagsFabricated );
   pHashTagsFabricated = NULL;
   g_hash_table_destroy( pHashFuncTagsAll );
   pHashFuncTagsAll = NULL;

   g_hash_table_foreach( pHashModules,
                         cb_FreeHashFuncTags,
                         NULL );
   return;
}

/*****************************************************************************
 * This is called as soon as the identifier of a function call is parsed.
 * Appends a funcCall_t struct to a list (see types.h).
 * Later, when the function definition is completely parsed,
 * AddFuncDefToGraph() will be called which will attach the func call list
 * to a funcDef_t struct.
 *****************************************************************************/
void
AddFuncCallToGraph( char* pFuncCallId,
                    int   lenFuncCallId )
{
   funcCall_t*	pFuncCall;
   tag_t*		pTag;

  /*
   * Preferably, try to find the tag of the parsed function call in the current module.
   */
   pTag = FindFuncTag( pFuncCallId, pModuleLoaded );
   if ( pTag == NULL )
   {
     /*
      * Try to find first matching function (or macro) tag in any module.
      */
      pTag = FindFuncTagAnyOtherModule( pFuncCallId, cur_pLinkModule );
   }

   if ( UX(pTag == NULL) )
   {
     /*
      * Tag wasn't found, so fabricate one.
      */
      pTag = FabricateFuncCallTag( pFuncCallId, lenFuncCallId );
   }

  /*
   * Allocate a funcCall_t struct.
   */
   pFuncCall = (funcCall_t*) g_malloc( sizeof(funcCall_t) );

  /*
   * Fill struct.
   */
   pFuncCall->pTagFuncCall = pTag;

  /*
   * Append struct to func call list (which is pointed to by funcDef_t).
   * (pGslistFuncCall must be NULLified before starting to parse a func def.)
   */
   pGslistFuncCall = g_slist_append( pGslistFuncCall, pFuncCall );

   ++cntFuncCalls;

   return;
}

/*****************************************************************************
 * Appends a funcDef_t struct to a list for the module being parsed
 * (head of list is pointed to by module_t).
 *
 * This is called when the parsing of a function definition completes,
 * which typically is after multiple calls to AddFuncCallToGraph().
 *
 * @param	pHashModules, pModuleLoaded (OUT/GLOBAL)
 *          Updates hash item's "pGslistFuncDef" member.
 *          pModuleLoaded points to hash item of module being parsed.
 *
 * @return	pGslistFuncCall (OUT/GLOBAL)
 *          NULLified before returning to prepare for next time.
 *****************************************************************************/
void
AddFuncDefToGraph( char* pFuncDefId,
                   int   lineLeftBrace,
                   int   lineRightBrace )
{
   funcDef_t*	pFuncDef;
   tag_t*		pTag;

#ifdef HYPERSRC_DEBUG /* too frequent for PEDANTIC */
   g_assert( pHashModules  &&  pModuleLoaded  &&  !IsStringEmpty(cur_pModuleName) );
   g_return_if_fail( pFuncDefId  &&  StrLen(pFuncDefId) < MAX_TAG_NAME_LEN );
   g_return_if_fail( lineLeftBrace  > 0  &&  lineLeftBrace  < MAX_LINES );
   g_return_if_fail( lineRightBrace > 0  &&  lineRightBrace < MAX_LINES );
   g_return_if_fail( lineLeftBrace <= lineRightBrace );
#endif

  /*
   * Find tag of parsed function definition.
   */
   pTag = FindFuncTag( pFuncDefId, pModuleLoaded );
   if ( EX( pTag  &&  !IsMacroTag(pTag) ) )
   {
     /*
      * Allocate a funcDef_t struct.
      */
      pFuncDef = (funcDef_t*) g_malloc( sizeof(funcDef_t) );

     /*
      * Fill struct.
      */
      pFuncDef->pTagFuncDef		= pTag;
      pFuncDef->pGslistFuncCall = pGslistFuncCall;	/* could stay NULL if def lacks calls */
      pFuncDef->lineStart		= lineLeftBrace;
      pFuncDef->lineLast		= lineRightBrace;

     /*
      * Append struct to func def list (pointed to by module_t).
      */
      pModuleLoaded->pGslistFuncDef = \
      g_slist_append( pModuleLoaded->pGslistFuncDef,
                      pFuncDef );
      ++cntFuncDefs;
   }

  /*
   * Prepare for the next function definition.
   */
   pGslistFuncCall = NULL; /* so that g_slist_append() will create a new gslist */
   return;
}

/*****************************************************************************
 * @return FALSE if a function identifier is actually a C keyword
 * in which case it shouldn't be graphed.
 *****************************************************************************/
int
ShouldFuncIdBeGraphed( char* pFuncId )
{
  /*
   * This func must be fast as possible.
   */
   static char keywords[] = "if"      /* in order of frequency */
                            "return"
                            "sizeof"
                            "while";
#if 0 /* infrequent or filtered-out by lex: */
                            "for"
                            "switch"
                            "defined";
#endif

   return strstr( keywords, pFuncId ) == NULL;
}

/*****************************************************************************
 * @param	First arg must be an  ASCIIZ string (even though length is passed).
 * @return	True if the name of a function isn't of a real function.
 *****************************************************************************/
gboolean
IgnoreFuncName( char* pFuncName/*ASCIIZ*/, int funcNameLen )
{
  /*
   * Bitwise AND.
   */
   if ( table1IgnoreFuncName[ (int)pFuncName[0] ] & table2IgnoreFuncName[ (int)pFuncName[1] ] )
   {
      return    STREQ( pFuncName, "if"      )
             || STREQ( pFuncName, "return"  )
             || STREQ( pFuncName, "sizeof"  )
             || STREQ( pFuncName, "while"   )
             || STREQ( pFuncName, "for"     )
             || STREQ( pFuncName, "switch"  )
             || STREQ( pFuncName, "defined" )
             || STREQ( pFuncName, "~"       ); /* eg x = ~(x|z) */
   }
   else
      return FALSE;
}


/*==============================================================================
 *
 * DEBUG.
 *
 *==============================================================================*/


#ifdef HYPERSRC_DEBUG

/*****************************************************************************
 * For debugging, print func graph.
 *****************************************************************************/
void
DumpFuncGraph( void )
{
   g_hash_table_foreach( pHashModules, CallbackDumpFuncGraph, NULL );
}
void
CallbackDumpFuncGraph( gpointer key,
                       gpointer val,
                       gpointer unused )
{
   module_t*	pModule = (module_t*) val;
   GSList*		pGslistFuncDefDbg	= NULL;
   GSList*		pGslistFuncCallDbg	= NULL;

  /*
   * This callback is passed a hash item from the module hash table.
   */
   DebugMsg( "\n------- module '%s' ---------------------------------------",
             pModule->pModuleName );

  /*
   * Get start of list of funcDef structs (from hash item).
   */
   pGslistFuncDefDbg = pModule->pGslistFuncDef;
   if ( pGslistFuncDefDbg == NULL )
   {
      DebugMsg( "(no function definitions were parsed or tags weren't found)\n" );
      return;
   }

  /*
   * For each funcDef struct in the list.
   */
   for ( ; pGslistFuncDefDbg; pGslistFuncDefDbg = pGslistFuncDefDbg->next )
   {
      funcDef_t*	pFuncDef = pGslistFuncDefDbg->data; /* pointer to funcDef per se */

      DebugMsg( "\n  %s(){..} defined at lines %d..%d",
                pFuncDef->pTagFuncDef->pName,
                pFuncDef->lineStart,
                pFuncDef->lineLast );

     /*
      * Get start of list of funcCall structs (pointed to by this funcDef struct).
      */
      pGslistFuncCallDbg = pFuncDef->pGslistFuncCall;
      if ( pGslistFuncCallDbg == NULL )
      {
         DebugMsg( "      (no func calls were parsed or tags weren't found)" );
         continue;
      }

     /*
      * For each parsed func call.
      */
      for ( ; pGslistFuncCallDbg; pGslistFuncCallDbg = pGslistFuncCallDbg->next )
      {
         funcCall_t* pFuncCall = pGslistFuncCallDbg->data; /* pointer to funcCall per se */

         DebugMsg( "      %s() called",
                   pFuncCall->pTagFuncCall->pName );
      }
   }
}

#endif /* HYPERSRC_DEBUG */
